#ifndef GPU_SKIN_INCLUDED
#define GPU_SKIN_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.mars.gpu-instance/Shader/Include/GPUInstancingDef.hlsl"

#if defined(UNITY_INSTANCING_ENABLED) && defined(_USE_INSTANCING_ANIM)
uint totalNumberOfBones;            //总的骨骼数
uint numberOfNonPropScaleBones;     //非等比缩放骨骼数
float4x4 bindPoseOffset;            //骨骼偏移

float currentTime;                  //当前时间
half frameRate;                     //帧率

// 骨骼动画数据，
// 动画 clip 数量为 n，骨骼数量为 m
// 第一个 float4 存储控制数据，x -> clip 数量
// 之后的 n 个 float4 数据，x -> clip动画起始index, y -> 非等比数据起始index, z -> clip等比缩放骨骼数量
// 最后是 (n*m + 1)/2 个 float4 数据，每 2 个 channel 存储一份 clipBoneData，包括 是否为非等比缩放 和 骨骼 indexInGroup
// 数据量 = 1 + clip 总数 + 总骨骼数 * clip 总数 / 2
CBUFFER_START(ClipBoneData)
uint4 _ClipBoneData[1024];              
CBUFFER_END

uint animLod = 0;                   //动画 LOD
#define ANIM_LOD_0 0
#define ANIM_LOD_1 1
#define ANIM_LOD_2 2

TEXTURE2D(_AnimTex);
float4 _AnimTex_TexelSize;
SamplerState sampler_gpu_skin_point_clamp;

float2 IndexToUV(int2 textureIndex)
{
    float2 uv =  float2(0, 0);
    uv.x = textureIndex.x * _AnimTex_TexelSize.x;
    uv.y = textureIndex.y * _AnimTex_TexelSize.y;
    return uv;
}

half4 QuatMul(half4 q1, half4 q2)
{
    return half4(
        q2.xyz * q1.w + q1.xyz * q2.w + cross(q1.xyz, q2.xyz),
        q1.w * q2.w - dot(q1.xyz, q2.xyz)
    );
}
   
// Vector rotation with a quaternion
// http://mathworld.wolfram.com/Quaternion.html
half3 RotateVector(half3 v, half4 r)
{
    half4 r_c = r * half4(-1, -1, -1, 1);
    return QuatMul(r, QuatMul(half4(v, 0), r_c)).xyz;
}

half4 SampleAnimTex(uint dataIndex)
{
    int2 texIndex;
    texIndex.x = (dataIndex % _AnimTex_TexelSize.z);
    texIndex.y = (floor(dataIndex / _AnimTex_TexelSize.z));
    float2 uv = IndexToUV(texIndex);
    half4 sample = SAMPLE_TEXTURE2D_LOD(_AnimTex, sampler_gpu_skin_point_clamp, uv, 0);

    return sample;
}

void TransformBone(uint curClipIndex, uint boneIndex, uint frame, inout half3 outVertex, inout half3 outNormal, inout half3 outTangent)
{
    uint clipCount = _ClipBoneData[0].x;
    
    float4 clipData = _ClipBoneData[curClipIndex + 1];
    const uint clipStartIndex = clipData.x;
    const uint nonPropScaleStartIndex = clipData.y;
    const uint propScaleBoneCount = clipData.z;

    uint boneDataIndex = 1 + clipCount + (curClipIndex * totalNumberOfBones + boneIndex) / 2;
    uint channelIndex = (curClipIndex * totalNumberOfBones + boneIndex) * 2 % 4;
    float4 boneData = _ClipBoneData[boneDataIndex];
    const bool nonPropScale = boneData[channelIndex] > 0.5;
    const uint boneIndexInGroup = boneData[channelIndex + 1];
    
    #if defined(_ANIM_NON_PROP_SCALING)
    if(!nonPropScale)
    #endif
    {
        // x == 0, 该骨骼在当前动画中是等比缩放骨骼，使用分量进行变换
        int dataIndex = clipStartIndex + (frame * propScaleBoneCount + boneIndexInGroup) * 2;
        half4 quaternion = SampleAnimTex(dataIndex);
        half4 offsetScale = SampleAnimTex(dataIndex+ 1);
        
        half3 scale = (half3)offsetScale.w;
        
        // apply scale
        outVertex = outVertex * scale;
    
        // apply rotation    
        outVertex = RotateVector(outVertex, quaternion);
        outNormal = normalize(RotateVector(outNormal, quaternion));
        outTangent = normalize(RotateVector(outTangent, quaternion));
    
        // apply translation
        outVertex += offsetScale.xyz;
    }
    #if defined(_ANIM_NON_PROP_SCALING)
    else
    {
        // x == 1, 该骨骼在当前动画中是非等比缩放骨骼
        uint nonPropBoneCount = totalNumberOfBones - propScaleBoneCount;
        uint dataIndex = nonPropScaleStartIndex + (frame * nonPropBoneCount + boneIndexInGroup) * 4;
        half4 col0 = SampleAnimTex(dataIndex);
        half4 col1 = SampleAnimTex(dataIndex + 1);
        half4 col2 = SampleAnimTex(dataIndex + 2);
        half4 col3 = SampleAnimTex(dataIndex + 3);

        float4x4 boneMatrix = float4x4(
            col0.x, col1.x, col2.x, col3.x,
            col0.y, col1.y, col2.y, col3.y,
            col0.z, col1.z, col2.z, col3.z,
            col0.w, col1.w, col2.w, col3.w
        );

        float3x3 boneMat3 = (float3x3) boneMatrix;
        outNormal = normalize(mul(boneMat3, outNormal));
        outVertex = mul(boneMat3, outVertex) +  boneMatrix._14_24_34;
    }
    #endif
}

void TransformFrame(uint clipIndex, float4 boneIndex, float4 boneWeight, uint frame, inout half3 outVertex, inout half3 outNormal, inout half3 outTangent)
{
    half3 vertex0 = outVertex, normal0 = outNormal, tangent0 = outTangent;
    TransformBone(clipIndex,(uint)boneIndex.x, frame, vertex0, normal0, tangent0);
    
    if(animLod >= ANIM_LOD_2)
    {
        // 在非低端机但是视距LOD大于等于2时，只使用第一个骨骼
        outVertex = vertex0;
        outNormal = normal0;
        outTangent = tangent0;
        return;
    }
    half3 vertex1 = outVertex, normal1 = outNormal, tangent1 = outTangent;
    if(boneWeight.y > 0)
    {
        // 在非低端机且视距LOD小于2时，允许第二个骨骼进行混合
        TransformBone(clipIndex, (uint)boneIndex.y, frame, vertex1, normal1, tangent1);
    }
    
    outVertex = lerp(vertex0, vertex1, boneWeight.y);
    outNormal = lerp(normal0, normal1, boneWeight.y);
    outTangent = lerp(tangent0, tangent1, boneWeight.y);

}

void gpuiCASkinning(float4 boneIndex, float4 boneWeight, float3 vertex, float3 normal, float3 tangent, out float3 out_vertex, out float3 out_normal, out float3 out_tangent)
{
    uint addition = totalNumberOfBones;
    
    #ifdef GPU_BINDPOSEOFFSET
    vertex = mul(bindPoseOffset, vertex);
    normal = mul(bindPoseOffset, normal);
    tangent = mul(bindPoseOffset, tangent);
    #endif

    const float4 animationData = ACCESS_INSTANCED_PROP(_AnimationData);
    const float4 curClipData = ACCESS_INSTANCED_PROP(_CurAnimClipData);
    const uint curClipIndex = (uint)curClipData.x;
    const int frame = max(0, (int)animationData.x);
    
    out_vertex = vertex;
    out_normal = normal;
    out_tangent = tangent;
    TransformFrame(curClipIndex, boneIndex, boneWeight, frame, out_vertex, out_normal, out_tangent);
    
    // 在高端机上，近距离时允许 CrossFade，混合两帧动画
    if(animLod == ANIM_LOD_0 && animationData.w > 0)
    {
         const uint curClipIndex1 = (uint)curClipData.y;
         const int frame1 = max(0, (int)animationData.z);
         half3 vertex1 = vertex, normal1 = normal, tangent1 = tangent;
         TransformFrame(curClipIndex, boneIndex, boneWeight, frame1, vertex1, normal1, tangent1);
         out_vertex = lerp(out_vertex, vertex1, animationData.w);
         out_normal = lerp(out_normal, normal1, animationData.w);
         out_tangent = lerp(out_tangent, tangent1, animationData.w);
    }
}
#else
void gpuiCASkinning(float4 boneIndex, float4 boneWeight, float3 vertex, float3 normal, float3 tangent, out float3 out_vertex, out float3 out_normal, out float3 out_tangent)
{
    out_vertex = vertex;
    out_normal = normal;
    out_tangent = tangent;
}
#endif

#endif